﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using zijian666.Core.Abstractions.Core;

namespace zijian666.Core
{
    internal class ConcurrentList<T> : IEnumerable<T>
    {
        private class DefaultEqualityComparer : IEqualityComparer<T>
        {
            public bool Equals(T x, T y)
            {
                if (ReferenceEquals(x, y))
                {
                    return true;
                }
                if (x is IEquatable<T> x1)
                {
                    return x1.Equals(y);
                }
                return x?.Equals(y) ?? false;
            }

            public int GetHashCode(T value) => value?.GetHashCode() ?? int.MinValue;
        }

        private static readonly DefaultEqualityComparer DefaultComparer = new DefaultEqualityComparer();

        public ConcurrentList(IEqualityComparer<T> comparer) => _comparer = comparer ?? DefaultComparer;

        private readonly IEqualityComparer<T> _comparer;
        private readonly List<T> _cache = new List<T>();
        private T[] _items = ArrayHelper<T>.ENPTY;

        public bool Remove(T item) => RemoveIf(x => _comparer.Equals(x, item)) > 0;

        public int RemoveIf(Predicate<T> predicate)
        {
            if (predicate == null)
            {
                return 0;
            }
            lock (_cache)
            {
                var count = _cache.Count;
                while (true)
                {
                    var index = _cache.FindLastIndex(predicate);
                    if (index <= 0)
                    {
                        if (index == 0)
                        {
                            _cache.RemoveAt(0);
                        }
                        var removed = count - _cache.Count;
                        if (removed > 0)
                        {
                            Reflush();
                        }
                        return removed;
                    }
                    _cache.RemoveAt(index);
                }
            }
        }

        public bool Add(T item)
        {
            if (Array.Exists(_items, x => _comparer.Equals(x, item)))
            {
                return false;
            }
            lock (_cache)
            {
                if (_cache.Any(x => _comparer.Equals(x, item)))
                {
                    return false;
                }
                _cache.Add(item);
                Reflush();
            }
            return true;
        }

        public IEnumerable<T> Clear()
        {
            lock (_cache)
            {
                if (_cache.Count == 0)
                {
                    return ArrayHelper<T>.ENPTY;
                }
                _cache.Clear();
                var items = _items;
                _items = ArrayHelper<T>.ENPTY;
                return items;
            }
        }

        public bool ReplaceOrAdd(T item)
        {
            lock (_cache)
            {
                var index = _cache.FindIndex(x => _comparer.Equals(x, item));
                if (index < 0)
                {
                    _cache.Add(item);
                    Reflush();
                    return false;
                }
                else
                {
                    _cache[index] = item;
                    _items[index] = item;
                    return true;
                }
            }
        }


        public int AddRange(IEnumerable<T> items)
        {
            if (_items.Intersect(items, _comparer).Any())
            {
                return 0;
            }
            lock (_cache)
            {
                var i = 0;
                foreach (var item in items)
                {
                    if (_cache.Any(x => _comparer.Equals(x, item)))
                    {
                        continue;
                    }
                    _cache.Add(item);
                }
                var count = _cache.Count - _items.Length;
                Reflush();
                return count;
            }
        }

        public int ReplaceOrAdd(IEnumerable<T> items)
        {
            var count = 0;
            lock (_cache)
            {
                foreach (var item in items)
                {
                    var index = _cache.FindIndex(x => _comparer.Equals(x, item));
                    if (index < 0)
                    {
                        _cache.Add(item);
                    }
                    else
                    {
                        _cache[index] = item;
                        if (index >= 0 && index < _items.Length)
                        {
                            _items[index] = item;
                            count++;
                        }
                    }
                }
                if (_cache.Count != _items.Length)
                {
                    Reflush();
                }
            }
            return count;
        }

        public bool Contains(T item) => Array.Exists(_items, x => _comparer.Equals(x, item));

        public bool Contains(Predicate<T> match) => Array.Exists(_items, match);

        private void Reflush() => _items = _cache.ToArray();

        public IEnumerator<T> GetEnumerator()
        {
            var items = _items;
            for (int i = 0; i < items.Length; i++)
            {
                yield return items[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();

        public IEnumerator<T> Reverse()
        {
            var items = _items;
            for (int i = items.Length - 1; i >= 0; i--)
            {
                yield return items[i];
            }
        }

        public int Count => _items.Length;

        public T this[int index] => index < 0 || index >= _items.Length ? default : _items[index];
    }
}
